﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using CsvSegítő.typeconverter;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CsvSegítő.Tests
{
    /// <summary>
    /// Summary description for CsvSegítő
    /// </summary>
    [TestClass]
    public class CsvSegítő
    {
        public CsvSegítő()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region TestModels
        private class TestAccount
        {
            public int AccountId { get; set; }
            public string AccountNumber { get; set; }
            public string AccountType { get; set; }
            public string AccountName { get; set; }
            public DateTime AccountOpenDateTime { get; set; }
        }

        private class TestAccountMapping : CsvMapping<TestAccount>
        {
            public TestAccountMapping()
            {
                MapProperty(0, x => x.AccountId);
                MapProperty(1, x => x.AccountNumber);
                MapProperty(2, x => x.AccountType);
                MapProperty(3, x => x.AccountName);
                MapProperty(4, x => x.AccountOpenDateTime);
            }
        }

        private class TestAccountMappingWithTypeConverterProvider : CsvMapping<TestAccount>
        {
            public TestAccountMappingWithTypeConverterProvider(ITypeConverterProvider tcp) : base(tcp)
            {
                MapProperty(0, x => x.AccountId);
                MapProperty(1, x => x.AccountNumber);
                MapProperty(2, x => x.AccountType);
                MapProperty(3, x => x.AccountName);
                MapProperty(4, x => x.AccountOpenDateTime);
            }
        }

        private class TestAccountMappingWithCustomConverter : CsvMapping<TestAccount>
        {
            public TestAccountMappingWithCustomConverter()
            {
                MapProperty(0, x => x.AccountId);
                MapProperty(1, x => x.AccountNumber);
                MapProperty(2, x => x.AccountType);
                MapProperty(3, x => x.AccountName);
                MapProperty(4, x => x.AccountOpenDateTime)
                    .WithCustomConverter(new DateTimeConverter("yyyy###MM###dd"));
            }
        }

        private class f135ab
        {
            public int RecordNumber { get; set; }
            public DateTime EntryDate { get; set; }
            public string Who { get; set; }
            public string Comment { get; set; }
            public int? NextRecord { get; set; }
            public int? LineNumber { get; set; }
            public int? Contact { get; set; }
            public int? EntryCdate { get; set; }
        }

        private class Testf135abMapping : CsvMapping<f135ab>
        {
            public Testf135abMapping()
            {
                MapProperty(0, x => x.RecordNumber);
                MapProperty(1, x => x.EntryDate);
                MapProperty(2, x => x.Who);
                MapProperty(3, x => x.Comment);
                MapProperty(4, x => x.NextRecord);
                MapProperty(5, x => x.LineNumber);
                MapProperty(6, x => x.Contact);
                MapProperty(7, x => x.EntryCdate);
            }
        }

        #endregion

        [TestMethod]
        public void BasicTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            TestAccountMapping csvMapper = new TestAccountMapping();
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);
            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime")
                .AppendLine("1;054382189489482;CC;Test Credit Account;2015/01/01")
                .AppendLine("2;095554465;IM;Test Credit Account 2;1998/01/01")
                .AppendLine("3;00008854542222;CC;Test Credit Account 3;1972/09/13");

            var result = csvParser.ReadFromString(csvReaderOptions, stringBuilder.ToString());
             
            Assert.IsNotNull(result);
        }

        [TestMethod]
        public void SkipHeaderTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            TestAccountMapping csvMapper = new TestAccountMapping();
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);

            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime")
                .AppendLine("1;054382189489482;CC;Test Credit Account;2015/01/01")
                .AppendLine("2;095554465;IM;Test Credit Account 2;1998/01/01")
                .AppendLine("3;00008854542222;CC;Test Credit Account 3;1972/09/13");

            var result = csvParser.ReadFromString(csvReaderOptions, stringBuilder.ToString())
                .ToList();


            Assert.AreEqual(3, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));

            Assert.AreEqual(1, result[0].Result.AccountId);
            Assert.AreEqual("054382189489482", result[0].Result.AccountNumber);

            Assert.AreEqual(2015, result[0].Result.AccountOpenDateTime.Year);
            Assert.AreEqual(01, result[0].Result.AccountOpenDateTime.Month);
            Assert.AreEqual(01, result[0].Result.AccountOpenDateTime.Day);

            Assert.AreEqual("IM", result[1].Result.AccountType);
            Assert.AreEqual("Test Credit Account 2", result[1].Result.AccountName);

            Assert.AreEqual(1972, result[2].Result.AccountOpenDateTime.Year);
            Assert.AreEqual(09, result[2].Result.AccountOpenDateTime.Month);
            Assert.AreEqual(13, result[2].Result.AccountOpenDateTime.Day);
        }

        [TestMethod]
        public void EmptyDataTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            TestAccountMapping csvMapper = new TestAccountMapping();
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);

            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime");
            var result = csvParser
                .ReadFromString(csvReaderOptions, stringBuilder.ToString())
                .ToList();

            Assert.AreEqual(0, result.Count);
        }

        [TestMethod]
        public void TrimLineTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' }, 1, true);
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            TestAccountMapping csvMapper = new TestAccountMapping();
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);

            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime")
                .AppendLine("1;054382189489482;CC;Test Credit Account;2015/01/01")
                .AppendLine("    2;095554465;IM;Test Credit Account 2;1998/01/01")
                .AppendLine("3;00008854542222;CC;Test Credit Account 3;1972/09/13   ");

            var result = csvParser
                .ReadFromString(csvReaderOptions, stringBuilder.ToString())
                .ToList();

            Assert.AreEqual(3, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));

            Assert.AreEqual(1, result[0].Result.AccountId);
            Assert.AreEqual("Test Credit Account", result[0].Result.AccountName);

            Assert.AreEqual(2, result[1].Result.AccountId);
            Assert.AreEqual("095554465", result[1].Result.AccountNumber);

            Assert.AreEqual("Test Credit Account 3", result[2].Result.AccountName);
            Assert.AreEqual(1972, result[2].Result.AccountOpenDateTime.Year);
            Assert.AreEqual(09, result[2].Result.AccountOpenDateTime.Month);
            Assert.AreEqual(13, result[2].Result.AccountOpenDateTime.Day);
        }

        [TestMethod]
        public void DateTimeTest_ProviderBased()
        {
            // Instantiate the TypeConverterProvider and override the DateTimeConverter:
            ITypeConverterProvider csvTypeConverterProvider =
                new TypeConverterProvider().Add(new DateTimeConverter("yyyy###MM###dd"));

            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            // Make sure to pass the custom provider into the mapping!
            var csvMapper = new TestAccountMappingWithTypeConverterProvider(csvTypeConverterProvider);
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);


            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime")
                .AppendLine("1;054382189489482;CC;Test Credit Account;2015###01###02");

            var result = csvParser
                .ReadFromString(csvReaderOptions, stringBuilder.ToString())
                .ToList();

            Assert.AreEqual(1, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));

            Assert.AreEqual(1, result[0].Result.AccountId);
            Assert.AreEqual("054382189489482", result[0].Result.AccountNumber);
            Assert.AreEqual(2015, result[0].Result.AccountOpenDateTime.Year);
            Assert.AreEqual(01, result[0].Result.AccountOpenDateTime.Month);
            Assert.AreEqual(02, result[0].Result.AccountOpenDateTime.Day);

        }

        [TestMethod]
        public void DateTimeTest_CustomConverterBased()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' }, 1, true);
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            TestAccountMappingWithCustomConverter csvMapper = new TestAccountMappingWithCustomConverter();
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);

            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime")
                .AppendLine("1;054382189489482;CC;Test Credit Account;2015###01###02");

            var result = csvParser
                .ReadFromString(csvReaderOptions, stringBuilder.ToString())
                .ToList();

            Assert.AreEqual(1, result[0].Result.AccountId);
            Assert.AreEqual("054382189489482", result[0].Result.AccountNumber);
            Assert.AreEqual(2015, result[0].Result.AccountOpenDateTime.Year);
            Assert.AreEqual(01, result[0].Result.AccountOpenDateTime.Month);
            Assert.AreEqual(02, result[0].Result.AccountOpenDateTime.Day);

        }

        [TestMethod]
        public void ParallelLinqTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
            TestAccountMapping csvMapper = new TestAccountMapping();
            CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);

            var stringBuilder = new StringBuilder()
                .AppendLine("AccountId;AccountNumber;AccountType;AccountName;AccountOpenDateTime")
                .AppendLine("1;054382189489482;CC;Test Credit Account;2015/01/01")
                .AppendLine("2;095554465;IM;Test Credit Account 2;1998/01/01")
                .AppendLine("3;00008854542222;CC;Test Credit Account 3;1972/09/13");

            var result = csvParser
                .ReadFromString(csvReaderOptions, stringBuilder.ToString())
                .Where(x => x.IsValid)
                .Where(x => x.Result.AccountNumber == "095554465")
                .ToList();

            Assert.AreEqual(1, result.Count);
            Assert.AreEqual("IM", result[0].Result.AccountType);
            Assert.AreEqual(1998, result[0].Result.AccountOpenDateTime.Year);
            Assert.AreEqual(01, result[0].Result.AccountOpenDateTime.Month);
            Assert.AreEqual(01, result[0].Result.AccountOpenDateTime.Day);

        }

        [TestMethod]
        public void ParallelismDegreeTest()
        {
            int csvDataLines = 1000000;
            int[] degreeOfParallelismList = new[] { 1, 2, 4 };

            StringBuilder stringBuilder = new StringBuilder();
            for (var i = 0; i < csvDataLines; i++)
            {
                stringBuilder.AppendLine("1;054382189489482;CC;Test Credit Account;2015/01/01");
            }

            var csvData = stringBuilder.ToString();

            foreach (var dop in degreeOfParallelismList)
            {
                CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' }, dop, true);
                CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
                TestAccountMapping csvMapper = new TestAccountMapping();
                CsvParser<TestAccount> csvParser = new CsvParser<TestAccount>(csvParserOptions, csvMapper);

                MeasureElapsedTime($"ParallelismDegreeTest (DegreeOfParallelism = {dop})", () => csvParser.ReadFromString(csvReaderOptions, csvData).ToList());
            }
        }

        [TestMethod]
        public void SequentialReadTest()
        {
            IEnumerable<string[]> a = null;
            MeasureElapsedTime("Sequential Read", () =>
            {
                a = File.ReadLines(@"Resources\f135ab.csv", Encoding.Default)
                    .AsParallel()
                    .Where(line => !string.IsNullOrEmpty(line))
                    .Select(line => line.Trim().Split(new[] { ',' }));
            });
        }

        [TestMethod]
        public void Csvf135abTest()
        {
            bool[] keepOrder = new bool[] { true, false };
            int[] dopl = new[] { 4, 3, 2, 1 };

            foreach (var order in keepOrder)
            {
                foreach (var dop in dopl)
                {
                    CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ',' }, dop, order);
                    CsvReaderOptions csvReaderOptions = new CsvReaderOptions(new[] { Environment.NewLine });
                    Testf135abMapping csvMapper = new Testf135abMapping();
                    CsvParser<f135ab> csvParser = new CsvParser<f135ab>(csvParserOptions, csvMapper);

                    MeasureElapsedTime($"Sequential Read (Degree of Parallelism = {dop}, Keep Order = {order})",
                        () => csvParser.ReadFromFile(@"Resources\f135ab.csv", Encoding.ASCII).ToList());
                }
            }
        }
        private void MeasureElapsedTime(string desc, Action action)
        {
            // Get the elapsed time as a TimeSpan value.
            TimeSpan ts = MeasureElapsedTime(action);

            // Format and display the TimeSpan value.
            string elapsedTime = $"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds / 10:00}";

            Console.WriteLine("[{0}] Elapsed Time = {1}", desc, elapsedTime);
        }

        private TimeSpan MeasureElapsedTime(Action action)
        {
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            action();
            stopWatch.Stop();

            return stopWatch.Elapsed;
        }

    }
}
